
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <stdatomic.h>

#include "../include/tinycthread.h"
#include "../include/snake.h"

#if defined(__WIN32__)
#include <windows.h>
#endif


int GotoXY(unsigned int const x,unsigned  int const y){
#if defined(__WIN32__)
    HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(ConsoleHandle,(COORD){.X=x,.Y=y});
#elif defined(unix)
    printf("\033[%d;%df\n",y,x);
#endif
    return 0;
}

int GotoPos(coord const dest){
#if defined(__WIN32__)      //Call the corresponding method according to the environment
    HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(ConsoleHandle,(COORD){.X=dest.x,.Y=dest.y});
#elif defined(unix)
    printf("\033[%d;%df\n",dest.y,dest.x);
#endif
    return 0;
}

int PutChar(int const Ch,coord const Pos){
    GotoPos(Pos);
    putchar(Ch);   //putch() will bring error
    return(0);
}

int IsHit(game *p_game){   //Judge whether snake hit himself
    node *p_current = p_game->p_snake_head;
    p_current = p_current->p_next_node;
    p_current = p_current->p_next_node;
    while (p_current){
        if (0 == memcmp(&p_current->location,&p_game->p_snake_head->location, sizeof(coord))){
            p_game->is_run = 0;
            return(1);
        }
        p_current = p_current->p_next_node;
    }
    return(0);
}

int IsFoodOnSnakeBody(game *p_game){  //Solve the bug that food occasionally appears on the Body
    node *p_current = p_game->p_snake_head;
    while (p_current){
        if (0 == memcmp(&p_current->location,&p_game->food, sizeof(coord))){
            return(1);
        }
        p_current = p_current->p_next_node;
    }
    return(0);
}

int CreatFood(game *p_game){  //Solve the bug that food occasionally appears on the Body
    //PutChar(' ',p_game->food);
    p_game->food.x = rand()%(p_game->canvas.x - 8)+3;
    p_game->food.y = rand()%(p_game->canvas.y - 8)+3;
    while (IsFoodOnSnakeBody(p_game)){
        p_game->food.x = rand()%(p_game->canvas.x - 8)+3;
        p_game->food.y = rand()%(p_game->canvas.y - 8)+3;
    }
    PutChar('X',p_game->food);

}

int GameSteer(game *p_game){    //Get input from the keyboard and change the direction of the snake head
    int c_input;
    int next_dirct = -1;
    while(p_game->is_run == 1){
        //mtx_lock(&p_game->mutex);
        switch ((char)getch()) {
            case 'h':{
                next_dirct = TOWARDS_LEFT;
                break;
            }
            case 'j':{
                next_dirct = TOWARDS_DOWN;
                break;
            }
            case 'k':{
                next_dirct = TOWARDS_UP;
                break;
            }
            case 'l':{
                next_dirct = TOWARDS_RIGHT;
                break;
            }
            case 'q':{
                p_game->is_run = 0;
                break;
            }
        }
        if (p_game->direction == next_dirct){  //Fast forward
            mtx_lock(&p_game->game_lock);
            SnakeMove(p_game);
            mtx_unlock(&p_game->game_lock);
        }
        if ((p_game->direction+next_dirct)%2 == 1){  //if the input direction is opposite to the current moving direction, discard this operation
            p_game->direction = next_dirct;
        }
        //mtx_unlock(&p_game->mutex);
    }
}

int SnakeMove(game *p_game){
    //Calculate the coordinates for the next location
    coord next_point;
    memcpy(&next_point,&p_game->p_snake_head->location, sizeof(coord));
    switch (p_game->direction) {
        case TOWARDS_LEFT:
            next_point.x--;
            break;
        case TOWARDS_DOWN:
            next_point.y++;
            break;
        case TOWARDS_UP:
            next_point.y--;
            break;
        case TOWARDS_RIGHT:
            next_point.x++;
            break;
    }

    //The display on the console moves a unit
    PutChar('*',next_point);
    if (0 == memcmp(&next_point,&p_game->food, sizeof(coord))){  //When encountering food, create a new node, length +1
        node *p = malloc(sizeof(node));
        p->p_next_node = p_game->p_snake_head;
        p_game->p_snake_head = p;
        p_game->length++;
        CreatFood(p_game);

        p_game->score += 1.0f;

    }else{        //No food is encountered, move the snake tail node to the snake head directly
        node *p_pre_node = p_game->p_snake_head,*p_current = p_pre_node->p_next_node;
        for (int i = 2; i < p_game->length; ++i) {
            p_current = p_current->p_next_node;
            p_pre_node = p_pre_node->p_next_node;
        }
        PutChar(' ',p_current->location);

        //The list tail node moves to the head
        p_pre_node->p_next_node = NULL;
        p_current->p_next_node = p_game->p_snake_head;
        p_game->p_snake_head = p_current;
    }
    memcpy(&p_game->p_snake_head->location,&next_point, sizeof(coord));

    return(0);
}

int GameInit(game *p_game){
    srand(time(NULL));

    //Initialize the game variable
    p_game->canvas.x = 100;
    p_game->canvas.y = 38;
    p_game->food.x = rand()%(p_game->canvas.x - 8) + 3;
    p_game->food.y = rand()%(p_game->canvas.y - 8) + 3;
    p_game->is_run = 1;
    p_game->time = 0;
    p_game->direction = TOWARDS_RIGHT;
    p_game->length=3;
    p_game->speed = 1;
    p_game->score = 0;

    //Initialize the game lock
    mtx_init(&p_game->game_lock,mtx_plain);

    //Draw the game canvas
    for (int i = 0; i < p_game->canvas.x+2; ++i) {
        PutChar('*',(coord){.x=i,.y=0});
    }
    putchar('\n');
    for (int i = 1; i <= p_game->canvas.y; ++i) {
        putchar('*');
        for (int j = 1; j <= p_game->canvas.x; ++j) {
            PutChar(' ',(coord){.x=j,.y=i});
        }
        putchar('*');
        putchar('\n');
    }
    for (int i = 0; i < p_game->canvas.x+2; ++i) {
        PutChar('*',(coord){.x=i,.y=p_game->canvas.y+1});
    }
    putchar('\n');

    //Initialize output
    puts("Score:");
    GotoXY(p_game->canvas.x-9,p_game->canvas.y+2);
    puts("Time:");

    //Create a new food
    CreatFood(p_game);

    //Initialize the SNAKE BODY
    p_game->p_snake_head = malloc(sizeof(node));
    node *p_current = p_game->p_snake_head;
    p_current->location.x = rand()%(p_game->canvas.x - 10) + 3;
    p_current->location.y = rand()%(p_game->canvas.y - 10) + 3;

    for (int i = 1; i <= 2; ++i) {
        p_current->p_next_node = malloc(sizeof(node));
        p_current = p_current->p_next_node;
        p_current->location.x = p_game->p_snake_head->location.x - i;
        p_current->location.y = p_game->p_snake_head->location.y;
    }

    p_current->p_next_node = NULL;

    return(0);

}

int GameRun(game *p_game){
    while (p_game->is_run){
        if(p_game->p_snake_head->location.x < 1 ||      //Determine if Snake hit the wall
           p_game->p_snake_head->location.x >=  p_game->canvas.x ||
           p_game->p_snake_head->location.y >=  p_game->canvas.y ||
           p_game->p_snake_head->location.y < 1 ||
           IsHit(p_game)
           ){
            p_game->is_run = 0;     //Stop when you hit the wall
        }else{
            mtx_lock(&p_game->game_lock);   //Lock to avoid console output conflict
            SnakeMove(p_game);
            mtx_unlock(&p_game->game_lock);
        }
        Sleep(1000/p_game->speed);
    }
    return(0);
}

int GameStop(game *p_game){
    struct tm *tm_game_time = localtime(&p_game->time);

    node *p_current = p_game->p_snake_head,*p_tmp;
    p_game->is_run = 0;
    while (p_current != NULL){
        p_tmp = p_current->p_next_node;
        free(p_current);
        p_current = p_tmp;
        p_game->length--;
    }
    p_game->p_snake_head = NULL;

    p_game->direction = TOWARDS_RIGHT;

    mtx_destroy(&p_game->game_lock);

    mktime(tm_game_time);
    GotoXY(0,p_game->canvas.y+4);
    printf("You played the game for %02d:%02d and reached %.2f points",tm_game_time->tm_min,tm_game_time->tm_sec,p_game->score);
    return(0);
}

int FreshGameInfo(game *p_game){
    struct tm *show_time;
    while (p_game->is_run == 1){
        Sleep(1000);

        p_game->time++;
        show_time = localtime(&p_game->time);

        mktime(show_time);
        mtx_lock(&p_game->game_lock);
        GotoXY(p_game->canvas.x-5,p_game->canvas.y+2);
        printf("%02d:%02d",show_time->tm_min,show_time->tm_sec);
        GotoXY(6,p_game->canvas.y+2);
        printf("%.2f",p_game->score);
        mtx_unlock(&p_game->game_lock);

        p_game->speed = 1 + p_game->score/10.0f;
    }

}

